5.4 Strukturen – eine Sonderform der Klassen  
Bisher haben wir Objekte erzeugt, indem wir eine Klasse instanziierten. Objekte beanspruchen Systemressourcen, denn mit jedem Objekt ist ein Verwaltungsoverhead verbunden. Bei einzelnen Objekten fällt das kaum ins Gewicht; ein Objekt-Array, das möglicherweise Tausende typgleicher Objekte enthält, kann die Systemleistung jedoch spürbar negativ beeinflussen.
In .NET gibt es aus diesem Grund ein Konstrukt, das sich ähnlich wie eine Klasse verhält, dabei jedoch ressourcenschonend agiert, andererseits aber auch bestimmten Einschränkungen unterliegt: die Struktur. Eine Struktur wird von der Laufzeitumgebung als Wertetyp behandelt.
5.4.1 Die Definition einer Struktur  
Eine Struktur beschreibt einen Datentyp, der sich aus Elementen verschiedener Typen zusammensetzt, die miteinander in einer festen Beziehung stehen und somit eine in sich geschlossene Einheit bilden. Stellen Sie sich vor, Sie wollten ein Automobil beschreiben, das durch einen Preis, Höchstgeschwindigkeit und Hubraum gekennzeichnet ist. Außerdem soll das Auto die Methoden Fahren und Hupen veröffentlichen. Die Klassendefinition könnte folgendermaßen lauten:
| Public Class Car
|
| Public Preis As Decimal
|
| Public Höchstgeschwindigkeit As Single
|
| Public Hubraum As Single
|
| Public Sub Fahren()
|
| ' Anweisungen
|
| End Sub
|
| Public Sub Hupen()
|
| ' Anweisungen
|
| End Sub
|
| End Class
|
Tatsächlich unterscheidet sich die analoge Definition einer Struktur Car kaum von der einer Klasse – abgesehen vom Austausch des Schlüsselwortes class durch Structure:
| Public Structure Car {
|
| ...
|
| End Structure
|
Im ersten Moment mag das zu der Schlussfolgerung verleiten, eine Klasse grundsätzlich durch eine Struktur ersetzen zu können. Wie im weiteren Verlauf dieses Kapitels aber noch gezeigt wird, haben Strukturen im Vergleich zu den Klassen geringere Fähigkeiten.
Die Bestätigung der Aussage, eine Struktur sei ein Wertetyp, wird schon bei der Deklaration einer Strukturvariablen deutlich. Würde der Typ Car als Klasse definiert, müsste vor dem ersten Aufruf eine Instanz durch das Aufrufen des Operators New erzeugt werden:
| ' Car liegt als Class-Definition vor
|
| Dim myCar As Car = New Car()
|
| myCar.Hubraum = 1900
|
Eine Struktur wird demgegenüber von der Laufzeitumgebung jedoch wie die Variable eines elementaren Datentyps eingesetzt, da kein Verweis damit verknüpft ist:
| ' Car liegt als Structure-Definition vor
|
| Dim myCar As Car
|
Der Zugriff auf die Elemente einer Struktur erfolgt ebenfalls mit dem Punktoperator, z. B.:
Um eine Struktur zu definieren, bieten sich folgende Möglichkeiten an:
|
eigenständig in einer separaten Quellcodedatei |
|
parallel neben anderen Klassen und Strukturen in einer Quellcodedatei |
|
im Deklarationsabschnitt einer Klasse |
Die lokale Definition innerhalb einer Methode ist hingegen nicht erlaubt und führt zu einer Fehlermeldung.
Eigenständige Strukturdefinition
Strukturen können entweder Public oder Friend sein. Wird kein Zugriffsmodifizierer angegeben, gilt eine Struktur Public.
Bei der Festlegung eines Feldes vom Typ einer Struktur in einer Klasse ist dem Zugriffsmodifizierer besondere Beachtung zu schenken. Betrachten Sie dazu das folgende Codefragment in einer Anwendung vom Typ Klassenbibliothek:
| Friend Structure Person
|
| Public Name as String
|
| Public Alter As Integer
|
| End Structure
|
| Public Class MyOwnClass
|
| Public Pers As Person
|
| End Class
|
Die Struktur Person ist als Friend definiert und beschränkt die Sichtbarkeit auf die aktuelle Assembly, in diesem Fall also auf die Klassenbibliothek. Die Klasse MyOwnClass ihrerseits ist Public definiert.
Der Code birgt in sich einen Widerspruch. In der öffentlichen Klasse ist das öffentliche Feld Pers vom Typ Person enthalten. Die Sichtbarkeit des Typs Person ist per Definition jedoch auf die aktuelle Anwendung, also auf die Klassenbibliothek, begrenzt. Tatsächlich wird die Entwicklungsumgebung diesen Widerspruch auch sofort mit einer Fehlermeldung quittieren, da gegen die folgende Regel verstoßen wird:
|
Ist ein Feld vom Typ einer Struktur, kann das Feld nicht öffentlicher sein als die Definition der Struktur.
|
5.4.2 Initialisieren einer Struktur  
Eine Struktur ist die Beschreibung eines Datentyps. Um das Objekt einer Struktur nutzen zu können, benötigt man eine Variable dieses Typs. Im folgenden Beispiel ist die Struktur Person mit den Feldern Name und Alter definiert. In der Main-Methode wird die Variable pers vom Typ Person deklariert, und den Feldern werden Werte zugewiesen.
| Module Module1
|
| Sub Main()
|
| Dim pers As New Person
|
| pers.Name = "Willi Jakob"
|
| pers.Alter = 35
|
| End Sub
|
| End Module
|
| Structure Person
|
| Public Name As String
|
| Public Alter As Integer
|
| End Structure
|
Der Name (»Willi Jakob«) und das Alter (35) sind einem ganz bestimmten Element zugeordnet, nämlich pers. Das erinnert an die Instanzvariablen einer Klasse, die ebenfalls objektgebunden sind. Der Vergleich ist auch nicht falsch, denn eine Strukturvariable ist einem Objektverweis sehr ähnlich, was durch die zweite Variante, eine Variable vom Typ einer Struktur zu deklarieren, besonders deutlich wird:
| pers.Name = "Willi Jakob"
|
| pers.Alter = 35
|
Die Syntax ist dieselbe wie bei der Instanziierung einer Klasse und deutet bereits darauf hin, dass es innerhalb einer Struktur einen Konstruktor geben muss, der parameterlos ist. Mit New wird dieser Konstruktor aufgerufen, der, wie auch der Konstruktor einer Klasse, die Felder des Objekts initialisiert.
Deklarieren Sie ein Objekt vom Typ einer Struktur mit New, sind alle Felder initialisiert und ermöglichen einen sofortigen Zugriff. Verzichten Sie auf den New-Operator, muss einem Feld zuerst ein Wert zugewiesen werden, bevor es ausgewertet werden kann.
Aus diesem Grund lehnt der VB-Compiler die Kompilierung des folgenden Codefragments ab: In der vierten Anweisung wird versucht, den Inhalt des noch uninitialisierten Feldes an der Konsole anzuzeigen.
| Dim pers1 As Person = New Person()
|
| Dim pers2 As Person
|
| Console.WriteLine(pers1.Name)
|
| Console.WriteLine(pers2.Name)
|
5.4.3 Ereignisse in einer Struktur  
Eine Struktur ist mit Fähigkeiten ausgestattet, die denen einer Klasse ähnlich sind: Sie kann Konstruktoren, Methoden, Eigenschaften und Ereignisse enthalten. Der Zugriff auf die Strukturmitglieder erfolgt in derselben Weise wie bei einer Klasse – nur die Fähigkeit eines auf einer Struktur basierenden Objekts, Ereignisse auslösen zu können, unterliegt anderen Richtlinien, denn die Objektvariable darf nicht mit WithEvents deklariert werden. Stattdessen werden die Ereignishandler mit AddHandler beim betreffenden Objekt registriert.
Im folgenden Codefragment wird in der Struktur EventTest das Ereignis SayHallo definiert, das beim Aufruf der Methode DoEvent ausgelöst wird:
| Public Structure EventTest
|
| Public Event SayHallo()
|
| Public Sub DoEvent()
|
| RaiseEvent SayHallo()
|
| End Sub
|
| End Structure
|
Wäre EventTest eine Klasse, könnten wir mit
| Dim WithEvents obj As EventTest
|
auf die ausgelösten Ereignisse reagieren. Weil EventTest aber als Struktur vorliegt, erzeugt diese Art der Deklaration einen Fehler und nötigt uns, den Weg über AddHandler zu beschreiten, beispielsweise:
| Sub Main()
|
| Dim obj As EventTest
|
| AddHandler obj.SayHallo, AddressOf MyEventProc
|
| obj.DoEvent()
|
| Console.ReadLine()
|
| End Sub
|
| Public Sub MyEventProc()
|
| Console.WriteLine("Ich wurde von einem EventTest-Objekt ausgelöst")
|
| End Sub
|
5.4.4 Änderung der Klasse »Circle«  
Wir wollen uns nun erneut der Klasse Circle zuwenden. Grundsätzlich wäre es denkbar, die Klasse Circle in eine Strukturdefinition umzuschreiben. Da wir Circle jedoch später in eine Vererbungshierarchie zwingen, kommt diese Änderung nicht in Betracht, da Strukturen grundsätzlich nicht abgeleitet werden können. An einer anderen Stelle bietet es sich allerdings an, die aktuelle Implementierung durch eine Struktur zu ersetzen: Es handelt sich dabei um die beiden Mittelpunktkoordinaten intXKoordinate und intYKoordinate, die nun durch die Struktur Point beschrieben werden sollen.
| Hinweis
|
|
In der .NET-Klassenbibliothek ist mit System.Drawing.Point eine sehr ähnliche Struktur bereits vordefiniert und würde sich gleichermaßen anbieten.
|
Point ist sehr einfach aufgebaut und hat nur die beiden Felder xKoordinate und yKoordinate, die später den Mittelpunkt eines Kreisobjekts beschreiben sollen. Außerdem enthält die Struktur einen zweiparametrigen Konstruktor, dem beim Aufruf die Punktkoordinaten übergeben werden.
| Public Structure Point
|
| Public X As Integer
|
| Public Y As Integer
|
| Public Sub New(ByVal xKoordinate As Integer, _
|
| ByVal yKoordinate As Integer)
|
| X = xKoordinate
|
| Y = yKoordinate
|
| End Sub
|
| End Structure
|
In der Klasse Circle zieht unsere Absicht selbstverständlich Änderungen nach sich. Zunächst werden die beiden Felder intXKoordinate und intYKoordinate durch ein Feld vom Typ der Struktur Point ersetzt, also:
Von der Einführung der Struktur Point ist auch der Konstruktor betroffen, der die beiden Mittelspunktkoordinaten in seinen Parametern erwartet.
| Public Sub New(ByVal radius As Double, ByVal x As Integer, _
|
| ByVal y As Integer)
|
| Me.New(radius)
|
| center.X = x
|
| center.Y = y
|
| End Sub
|
Die beiden Eigenschaften XKoordinate und YKoordinate sollen auch weiterhin die Möglichkeit bieten, ein Kreisobjekt horizontal oder vertikal zu verschieben bzw. die einzelnen Positionswerte zu ermitteln. Die Implementierung muss auch hier an das neue Feld center angepasst werden.
| Public Property XKoordinate() As Integer
|
| Get
|
| Return center.X
|
| End Get
|
| Set(ByVal Value As Integer)
|
| center.X = Value
|
| End Set
|
| End Property
|
| Public Property YKoordinate() As Integer
|
| Get
|
| Return center.Y
|
| End Get
|
| Set(ByVal Value As Integer)
|
| center.Y = Value
|
| End Set
|
| End Property
|
Eine gute Klassendefinition zeichnet sich nicht nur dadurch aus, die Implementierung auf das Notwendigste zu beschränken, sondern deckt auch die Fälle ab, die für einen Benutzer unter Umständen sinnvoll sein könnten. Sehen wir uns dazu an dieser Stelle noch einmal die beiden Eigenschaften XKoordinate und YKoordinate an. Soll der Mittelpunkt eines Kreisobjekts diagonal verschoben werden, sind zwei Anweisungen notwendig. Vorteilhafter ist es, dasselbe mit einer Anweisung zu erreichen. Die Verbesserung soll durch eine Methode erzielt werden, die wir als MoveXY bezeichnen und die ein Point-Objekt vom Aufrufer entgegennimmt:
| Public Sub MoveXY(ByVal newCenterPoint As Point)
|
| center = newCenterPoint
|
| End Sub
|
Da eine Struktur ein Wertetyp ist, schreiben sich die Felder X und Y der im Parameter newCenterPoint übergebenen Koordinaten in die gleich lautenden Felder von center.
Sehen wir uns nun in einem Codefragment an, wie einfach es ist, diese Methode zu benutzen. Es wird dabei davon ausgegangen, dass ein konkretes Circle-Objekt namens kreis vorliegt. Mit
| Dim newPoint As Point = New Point(150, 315)
|
| kreis.MoveXY(newPoint)
|
übergeben wir der Methode ein Point-Objekt. Benötigen wir dieses Objekt zur Laufzeit der Anwendung nicht mehr, kann es auch in der Argumentliste erzeugt werden.
| kreis.MoveXY(New Point(150, 315))
|
Zuletzt ergänzen wir die Klasse Circle noch um einen Konstruktor, der neben dem Radius eine Point-Referenz als Argument erwartet:
| Public Sub New(ByVal radius As Double, ByVal pt As Point)
|
| Me.New(radius)
|
| center = pt
|
| End Sub
|
Damit wird eine Instanziierung der Klasse mit
| Dim kreis As Circle = New Circle(2, New Point(5, 12))
|
möglich.
Den Code des Beispiels CircleApplication mit allen Änderungen, die wir bisher in diesem Kapitel vorgenommen haben, finden Sie auf der Buch-CD unter:
...\Kapitel 5\CircleApplication_3
5.4.5 Zusammenfassung der Unterscheidungsmerkmale Klasse – Struktur  
Klassen und Strukturen stellen in vielerlei Hinsicht dieselben Möglichkeiten zur Verfügung. Beispielsweise können in Strukturen Methoden und Ereignisse definiert sowie Ereignishandler implementiert werden. Die Methoden können nach denselben Regeln wie in den Klassen überladen werden.
Wie aber eingangs dieses Abschnitts bereits angedeutet wurde, gibt es einige Einschränkungen, die in Kauf genommen werden müssen, wenn Sie sich für eine Struktur anstelle einer Klasse entscheiden:
|
Strukturen können weder die Eigenschaften und Methoden anderer Klassen oder Strukturen erben, noch können sie ihre eigenen Features anderen Klassen oder Strukturen vererben. Von dieser Regel gibt es nur eine Ausnahme: Implizit beerben Strukturen die Klasse ValueType, die ihrerseits direkt aus der Klasse Object abgeleitet ist und deren Methoden in passender Weise überschreibt. |
|
Standardmäßig stellt eine Struktur einen parameterlosen Konstruktor bereit, der mit |
Dim x As MyStruct
aufgerufen wird. Alternativ ist eine Strukturvariable aber auch mit
Dim x As New MyStruct()
deklarierbar, was wiederum die Ähnlichkeit zu den Klassen verdeutlicht. In einer Struktur dürfen weitere Konstruktoren definiert werden. Diese müssen jedoch parametrisiert sein, denn das Überschreiben des parameterlosen Konstruktors ist nicht zulässig.
|
Wird ein parametrisierter Konstruktor einer Struktur aufgerufen, muss die Variable mit dem New-Operator initialisiert werden. Aber Vorsicht ist hierbei geboten, denn das folgende Codefragment führt zu einer zweifachen Initialisierung der Variablen myStruct: |
Dim myOwnStruct As MyStruct
myOwnStruct = New MyStruct(2)
In der ersten Zeile wird der parameterlose Konstruktor der Struktur aufgerufen, in der zweiten ein parametrisierter. Wäre der zugrunde liegende Typ MyStruct eine Klasse, würde es nur zu einem Konstruktoraufruf kommen.
|
Der Gültigkeitsbereich von Eigenschaften und Methoden wird durch die Modifizierer Public, Friend und Private beschrieben, bei Datenmembern gilt zusätzlich noch Dim. Mit Dim deklarierte Datenmember einer Struktur gelten immer als öffentlich, sind also Public. Zur Erinnerung: In einer Klassendefinition wäre eine so deklarierte Instanzvariable Private. |
|
Sollte es erforderlich sein, können Sie Stukturen auch ineinander verschachteln. Die einzelnen Ebenen werden – wie üblich – durch Punktnotation voneinander getrennt. |
|